﻿#region Using Directives
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
#endregion

namespace DarkMatter.Tests
{
    [DebuggerDisplay("NodeId = {NodeId}, RemoteNodeId = {RemoteNodeId}, IsConnected = {IsConnected}")]
    public class MockChannel : IChannel
    {
        public event BytesRecievedCallback BytesRecieved;

        private readonly string _nodeId;
        private bool _isConnected;
        private MockChannel _remoteChannel;

        public MockChannel(string nodeId)
        {
            _nodeId = nodeId;
        }

        public bool Accept()
        {
            if (_isConnected) return false;
 
            _isConnected = true;
            return IsConnected;
        }

        public void Send(byte[] bytes)
        {
            if (!IsConnected) throw new ChannelException("Not connected.");

            _remoteChannel.FireBytesRecievedEvent(bytes);

            if (_stream != null)
            {
                _stream.Write(bytes, 0, bytes.Length);
            }
        }

        public void FireBytesRecievedEvent(byte[] bytes)
        {
            BytesRecievedCallback handler = BytesRecieved;
            if (handler != null)
            {
                foreach (BytesRecievedCallback d in handler.GetInvocationList())
                {
                    try
                    {
                        d.Invoke(this, bytes);
                    }
                    catch (Exception) { }
                    
                }
            }
        }

        public string NodeId
        {
            get { return _nodeId; }
        }

        public string RemoteNodeId
        {
            get { return _remoteChannel.NodeId; }
        }

        public bool IsConnected
        {
            get { return _isConnected && _remoteChannel._isConnected; }
        }

        private MemoryStream _stream;

        public Stream Stream
        {
            get
            {
                if (_stream == null)
                {
                    _stream = new MemoryStream();
                }

                return _stream;
            }
        }

        // *****************************************************
        // Special methods to facilitate connection mocking.
        // These methods are not part of the IChannel interface.
        //
        // *****************************************************

        public void SetRemoteChannel(MockChannel channel)
        {
            _remoteChannel = channel;
        }

        public IChannel CreateRemoteChannel(string nodeId)
        {
            if (_isConnected)
            {
                throw new ChannelException(
                    "Already connected. Channels are point-to-point connections, meaning that a 3rd connection is not possible.");
            }

            MockChannel channel = new MockChannel(nodeId);
            
            channel.SetRemoteChannel(this);
            SetRemoteChannel(channel);

            return channel;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof(MockChannel)) return false;
            return Equals((MockChannel) obj);
        }

        public bool Equals(MockChannel obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj._remoteChannel._nodeId != _remoteChannel._nodeId || obj._remoteChannel._isConnected != _remoteChannel._isConnected)
            {
                return false;
            }
            return Equals(obj._nodeId, _nodeId) && obj._isConnected.Equals(_isConnected);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int result = (_nodeId != null ? _nodeId.GetHashCode() : 0);
                result = (result * 397) ^ (_remoteChannel._nodeId != null ? _remoteChannel._nodeId.GetHashCode() : 0);
                result = (result*397) ^ _remoteChannel._isConnected.GetHashCode();
                result = (result*397) ^ _isConnected.GetHashCode();
                return result;
            }
        }
    }
}
